﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Diagnostics.Contracts;
using SimpleIR;

namespace PInfoModel
{
	/// <summary>
	/// This class encapsulates the notion of location identity (both external and internal).
	/// </summary>
	public struct MemLocID : IEquatable<MemLocID>
	{
		private enum LocTag
		{
			External,
			Internal
		};

		private readonly LocTag m_tag;
		private readonly int m_id;
		private readonly int m_esclineinfo;
		private readonly IRRootIdentifier m_homefor;
		
		private MemLocID(LocTag source, int id, int einfo, IRRootIdentifier hfor)
		{
			this.m_tag = source;
			this.m_id = id;
			this.m_esclineinfo = einfo;
			this.m_homefor = hfor;
		}

		public override string ToString()
		{
			if(this.m_tag == LocTag.External)
				return "ExtLoc#" + this.m_id.ToString();
			else
				return "AllocLoc#" + this.m_id.ToString() + "@EscLine=" + this.m_esclineinfo + (this.m_homefor != null ? ("HomeFor=" + this.m_homefor.ToString()) : "");
		}

		/// <summary>
		/// Write this memory location to an xml stream for export to an external application.
		/// </summary>
		public void XMLExport(System.Xml.XmlWriter xw)
		{
			xw.WriteStartElement("MemLoc");
			this.XMLExportAttributesOnly(xw);
			xw.WriteEndElement();
		}

		/// <summary>
		/// Write this memory location to an xml stream for export to an external application.
		/// </summary>
		public void XMLExportAttributesOnly(System.Xml.XmlWriter xw)
		{
			if(this.m_tag == LocTag.External)
			{
				xw.WriteAttributeString("LocTag", "External");
				xw.WriteAttributeString("ExtID", this.m_id.ToString());
			}
			else
			{
				xw.WriteAttributeString("LocTag", "Internal");
				xw.WriteAttributeString("EscID", this.m_id.ToString());
				xw.WriteAttributeString("EscLine", this.m_esclineinfo.ToString());
				if(this.m_homefor != null)
					xw.WriteAttributeString("OptVTag", this.m_homefor.ToString());
			}
		}

		public bool IsExternalID
		{ get { return this.m_tag == LocTag.External; } }

		public bool IsInternalID
		{ get { return this.m_tag == LocTag.Internal; } }

		public bool IsHomeLocationId
		{ get { return this.m_homefor != null; } }

		public int ExternalIDValue
		{ get { return this.m_id; } }

		public int InternalIDValue
		{ get { return this.m_id; } }

		public int EscapeLineValue
		{ get { return this.m_esclineinfo; } }

		public IRRootIdentifier HomeLocationFor
		{ get { return this.m_homefor; } }


		/// <summary>
		/// Build a new source location that represents a memory location that was passed in from the caller scope.
		/// </summary>
		public static MemLocID MakeExternalMemLocation(int extid)
		{ return new MemLocID(LocTag.External, extid, 0, null); }

		/// <summary>
		/// Build a new source location that represents a memory location that was allocated (or escaped) in the callee.
		/// </summary>
		public static MemLocID MakeInternalMemLocation(int allocid, int escline, IRRootIdentifier hfor)
		{ return new MemLocID(LocTag.Internal, allocid, escline, hfor); }

		public static bool operator ==(MemLocID ml1, MemLocID ml2)
		{ return ml1.Equals(ml2); }

		public static bool operator !=(MemLocID ml1, MemLocID ml2)
		{ return !ml1.Equals(ml2); }

		public override bool Equals(object obj)
		{ return (obj is MemLocID) && this.Equals((MemLocID)obj); }

		public override int GetHashCode()
		{ return ((int)this.m_tag) ^ this.m_id ^ this.m_esclineinfo ^ (this.m_homefor != null ? this.m_homefor.GetHashCode() : 0); }

		public bool Equals(MemLocID other)
		{ return (this.m_tag == other.m_tag) & (this.m_id == other.m_id) & (this.m_esclineinfo == other.m_esclineinfo) & (this.m_homefor == other.m_homefor); }
	}

	/// <summary>
	/// This class encapsulates the notion of location identity (both external and internal).
	/// </summary>
	public struct MemLocWPath : IEquatable<MemLocWPath>
	{
		public readonly MemLocID MemLocation;
		public readonly IRBasicAccessPath AccPath;

		private MemLocWPath(MemLocID loc, IRBasicAccessPath bap)
		{
			this.MemLocation = loc;
			this.AccPath = bap;
		}

		public override string ToString()
		{
			return "(" + this.MemLocation.ToString() + ")" + this.AccPath.ToString();
		}

		/// <summary>
		/// Build a source location and specific access path in the location.
		/// </summary>
		public static MemLocWPath MakeMemLocWithPath(MemLocID ll, IRBasicAccessPath bap)
		{ return new MemLocWPath(ll, bap); }

		/// <summary>
		/// Build a source location and specific access path in the location for each possible terminal access path in the location.
		/// </summary>
		public static void AddAllTerminalMemLocsWithPath(MemLocID ll, IRType loctype, IRBasicAccessPath initp, IRProgram p, HashSet<MemLocWPath> into)
		{
			if(initp == null)
			{
				if(loctype is IRClassType || loctype is IRStructType)
				{
					foreach(IRBasicAccessPath bap in loctype.TypeDescription.AllTerminalAccessPaths)
						into.Add(MemLocWPath.MakeMemLocWithPath(ll, bap));
				}
				else
				{
					into.Add(MemLocWPath.MakeMemLocWithPath(ll, IRBasicAccessPath.EmptyAccessPath));
				}
			}
			else
			{
				if(loctype is IRClassType || loctype is IRStructType)
				{
					foreach(IRBasicAccessPath bap in loctype.TypeDescription.AllTerminalAccessPaths)
					{
						IRBasicAccessPath extp = p.GetAccessPathExtensionPost_WPath(initp, bap);
						into.Add(MemLocWPath.MakeMemLocWithPath(ll, extp));
					}
				}
				else
				{
					into.Add(MemLocWPath.MakeMemLocWithPath(ll, initp));
				}
			}
		}

		public static bool operator ==(MemLocWPath ml1, MemLocWPath ml2)
		{ return ml1.Equals(ml2); }

		public static bool operator !=(MemLocWPath ml1, MemLocWPath ml2)
		{ return !ml1.Equals(ml2); }

		public override bool Equals(object obj)
		{ return (obj is MemLocWPath) && this.Equals((MemLocWPath)obj); }

		public override int GetHashCode()
		{ return this.MemLocation.GetHashCode() ^ this.AccPath.GetHashCode(); }

		public bool Equals(MemLocWPath other)
		{ return (this.MemLocation == other.MemLocation) & (this.AccPath == other.AccPath); }
	}
}
